/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package yuhuibear.poat.query;

import java.sql.Array;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.sql.DataSource;

import yuhuibear.poat.exception.ParametersMissMatch;
import yuhuibear.poat.exception.PoatException;
import yuhuibear.poat.object.bean.PersistentClassLoader;

/**
 * 
 * @author yuhuibear
 */
public class ParamArranger {

	// private BeanAnalyzer bAnalyzer;
	private ParameterTypeBuffer parametersBuf;

	private PersistentClassLoader structfactory;

	private DataSource directSource;

	private ThreadLocal<Connection> conn = new ThreadLocal<Connection>();

	public ParamArranger(PersistentClassLoader structs, DataSource directSource) {
		this.directSource = directSource;
		this.structfactory = structs;
		parametersBuf = new ParameterTypeBuffer();

	}

	private synchronized Connection getCon() throws SQLException {
		Connection con = conn.get();
		if (con == null) {
			con = directSource.getConnection();
			conn.set(con);
		} else if (con.isClosed()) {
			conn.remove();
			con = directSource.getConnection();
			conn.set(con);
		}
		return con;
	}

	/**
	 * 向指定的sql 查询设定参数。
	 * 
	 * @param sql
	 * @param pst
	 * @param args
	 * 
	 * @throws java.lang.Exception
	 */
	public void setParameters(String sql, PreparedStatement pst, Object... args) throws Exception {
		int[] types = parametersBuf.getTypes(sql, pst);
		if (types.length < 1) {
			return;
		} else if (types.length == 1 && types[0] == java.sql.Types.ARRAY) {
			setParameterObject(pst, 1, args, types[0]);
		} else
			for (int i = 0; i < args.length; i++)
				setParameterObject(pst, i + 1, args[i], types[i]);

	}

	/**
	 * 向指定的sql 查询设定参数， 每个参数对象,如果是已被映射到数据库的类则其属性被分解出来，并作为一个参数设定到查询中。
	 * 
	 * @param sql
	 * @param pst
	 * @param arg
	 * 
	 * @throws java.lang.Exception
	 */
	public void setParametersAnalysed(String sql, PreparedStatement pst, Object... arg) throws Exception {
		int paramIndex = 0;
		int[] types = parametersBuf.getTypes(sql, pst);
		if (types.length < 1) {
			return;
		}
		for (int j = 0; j < arg.length; j++) {
			if (arg[j] == null) {
				continue;
			}
			if (structfactory.isDBEntity(arg[j].getClass())) {
				Map<String, Object> values = structfactory.analyse(arg[j].getClass()).getValues(arg[j]);
				String[] names = structfactory.analyse(arg[j].getClass()).getDBNameOfAttributes(arg[j]);
				for (int i = 0; i < names.length; i++) {
					setParameterObject(pst, i + 1, values.get(names[i]), types[i]);
					paramIndex++;
				}
			} else {
				setParameterObject(pst, paramIndex + 1, arg[j], types[paramIndex]);
				paramIndex++;
			}
		}
	}

	/**
	 * all parameters' name in sql stored in names Array with the same order.
	 * 
	 * @param brslt
	 * @param pst
	 * @param arg
	 * 
	 * @throws java.lang.Exception
	 */
	public void setParametersWithNames(BuilderResult brslt, PreparedStatement pst, Object arg) throws Exception {
		Map<String, Object> values = this.structfactory.analyse(arg.getClass()).getValues(arg);
		int[] types = parametersBuf.getTypes(brslt.sql, pst);
		if (types.length < 1) {
			return;
		}
		for (int i = 0; i < brslt.names.length; i++)
			setParameterObject(pst, i + 1, values.get(brslt.names[i]), types[i]);

	}

	private static Map<Class<?>, String> typeConverts = new HashMap<Class<?>, String>();
	static {
		typeConverts.put(new Long[] {}.getClass(), "bigint");
		typeConverts.put(new Integer[] {}.getClass(), "int");
		typeConverts.put(new String[] {}.getClass(), "text");
		typeConverts.put(new Double[] {}.getClass(), "double ");
	}

	private void setParameterObject(PreparedStatement pst, int col, Object value, int type) throws SQLException,
			PoatException {
		if (value == null) {
			pst.setObject(col, null, type);
			return;
		}
		switch (type) {
		case java.sql.Types.TIME: {
			if (java.util.Date.class.isAssignableFrom(value.getClass())) {
				pst.setTime(col, new java.sql.Time(((Date) value).getTime()));
			} else
				throw new ParametersMissMatch(String.format("[%s: %s] cann't convert to java.util.Time.class",
						value.getClass(), value));
			break;
		}
		case java.sql.Types.TIMESTAMP: {
			if (java.util.Date.class.isAssignableFrom(value.getClass())) {
				pst.setTimestamp(col, new java.sql.Timestamp(((Date) value).getTime()));
			} else
				throw new ParametersMissMatch(String.format("[%s: %s] cann't convert to java.util.Timestamp.class",
						value.getClass(), value));
			break;
		}
		case java.sql.Types.DATE: {
			if (java.util.Date.class.isAssignableFrom(value.getClass())) {
				pst.setDate(col, new java.sql.Date(((Date) value).getTime()));
			} else
				throw new ParametersMissMatch(String.format("[%s: %s] cann't convert to java.util.Date.class",
						value.getClass(), value));
			break;
		}
		case java.sql.Types.ARRAY: {
			if (!typeConverts.containsKey(value.getClass())) {
				try {
					Object obj = ((Object[]) value)[0];
					throw new PoatException(String.format("array of %s, not support yet!", obj.getClass().toString()));
				} catch (ClassCastException ce) {
					throw new ParametersMissMatch(String.format("need array, input %S.", value.getClass()), ce);
				}
			}
			Array a = getCon().createArrayOf(typeConverts.get(value.getClass()), (Object[]) value);
			pst.setArray(col, a);
			break;
		}
		default: {
			pst.setObject(col, value, type);
		}
		}
	}
}
